Repository navigation
Security patches (release/2.2.0-alpha1) - #405
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
wojcik91
force-pushed
the
release/2.2.0-alpha1
branch
from
September 30, 2026 09:31
f6a32cb to
eec0c5d
Compare
renovate
Bot
force-pushed
the
renovate/release/2.2.0-alpha1-security-patches
branch
from
September 30, 2026 09:36
f9ad32a to
d9f8145
Compare
renovate
Bot
force-pushed
the
renovate/release/2.2.0-alpha1-security-patches
branch
3 times, most recently
from
October 17, 2026 21:59
b111895 to
601ab7e
Compare
renovate
Bot
force-pushed
the
renovate/release/2.2.0-alpha1-security-patches
branch
3 times, most recently
from
October 23, 2026 21:01
d9d6cdb to
da06fa1
Compare
renovate
Bot
force-pushed
the
renovate/release/2.2.0-alpha1-security-patches
branch
from
October 27, 2026 23:13
da06fa1 to
84e2259
Compare
renovate
Bot
force-pushed
the
renovate/release/2.2.0-alpha1-security-patches
branch
from
October 29, 2026 01:54
84e2259 to
621d122
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This PR body was truncated due to platform limits.
This PR contains the following updates:
4.2.0→4.2.411.9.0→11.11.06.16.0→6.16.10.23.45→0.23.460.103.15→0.103.161.5.4→1.6.80.35.4→0.35.51.53.1→1.53.31.53.1→1.53.37.3.6→7.3.74.5.4→4.6.6Incorrect escaping for
url("")functions in CSS style attributesGHSA-69v2-w9vg-4g83 / RUSTSEC-2021-0157
More information
Details
In some cases, quotation marks in CSS url() functions are not escaped. This allows CSS injection. This feature is not enabled by default, so an application is vulnerable if, and only if, the style attribute is allowed and filter_style_properties is used.
As an example, this snippet:
Would erroneously "sanitize" to:
This vulnerability was reported by Uttej Badwane
Severity
Unknown
References
This data is provided by OSV and the Rust Advisory Database (CC0 1.0).
XSS in ammonia via SVG
animateMotion,animateTransform, and/oranimateColortagsGHSA-f2pc-rwv3-69mv / RUSTSEC-2021-0158
More information
Details
The neutralization that ammonia applied to
animateandsetwas not applied to other forms of SVG animation, which can be used for XSS.The following SVG will produce a link with a javascript scheme.
If the user clicks this link, they will run it.
Ammonia did not apply attribute filters based on
attributeName,so the contents of the
to,from, andvaluestags were not sanitized as URLs.Applications that do not explicitly allow any of these tags should not be affected,
since none are allowed by default.
This vulnerability was reported by ucsurf.
Severity
Unknown
References
This data is provided by OSV and the Rust Advisory Database (CC0 1.0).
Missing case-insensitive matching for CSS url function
GHSA-x43w-36pp-96g3 / RUSTSEC-2026-0373
More information
Details
URL filtering, such as relative URLs and URL scheme limits,
is not always applied when CSS sanitization is used.
When the inner URL is quoted, CSS
url("")is matched case-sensitively,when it should be matched case-insensitively.
As a result, while
url("")gets the intended domain name filtering,URL("")does not.The following example shows how an application needs to be configured
to trigger this bug.
This can be used to inject tracking pixels where the application intended to prevent them.
It does not allow XSS, and its usefulness for data exfiltration is limited.
This vulnerability was reported by Louis Li.
Severity
Unknown
References
This data is provided by OSV and the Rust Advisory Database (CC0 1.0).
pnpm: Virtual store linker path traversal via unvalidated depPath name in lockfileToDepGraph
CVE-2026-82392 / GHSA-c59q-g84q-2gj5
More information
Details
Summary
The virtual store linker constructs package installation directories using
path.join(modules, pkgName)wherepkgNameis extracted from lockfilepackageskeys viadp.parse(depPath).namewithout validation. A craftedpnpm-lock.yamlwith traversal sequences in depPath keys (e.g.,../../../tmp/pwned@1.0.0) causes package content to be written to arbitrary filesystem paths duringpnpm install.This is an incomplete fix of GHSA-fr4h-3cph-29xv — the
safeJoinModulesDircontainment helper was applied to the hoisted linker andsymlinkDependencybut NOT to the virtual store linker'slockfileToDepGraph.ts:233.Details
Root Cause
dp.parse()atpnpm11/deps/path/src/index.ts:135extracts the package name as:This is a raw substring operation with zero validation that
nameis a valid npm package name. A depPath of../../../tmp/pwned@1.0.0yieldsname = '../../../tmp/pwned'.Vulnerable Code Path
pnpm-lock.yaml→lockfile.packages['../../../../../../../tmp/pwned@1.0.0'](attacker-controlled lockfile key)nameVerFromPkgSnapshot(depPath, pkgSnapshot)atlockfile/utils/src/nameVerFromPkgSnapshot.ts:16→ callsdp.parse(depPath)→ returns{ name: '../../../../../../../tmp/pwned' }lockfileToDepGraph.ts:232→modules = path.join(dirInVirtualStore, 'node_modules')lockfileToDepGraph.ts:233→dir = path.join(modules, pkgName)→ resolves to/tmp/pwned(ESCAPES virtual store)storeController.importPackage(depNode.dir, ...)→ writes package content to the traversed pathWhy Existing Defenses Don't Catch It
depPathToFilename()— replaces/with+for thedirInVirtualStorepath, butpkgNamecomes SEPARATELY fromdp.parse()and is NOT passed through this functionverifyLockfileResolutions()— validates dependency map keys (aliases) viaisValidDependencyAlias(), but never validates the depPath keys themselvesyaml.load(lockfileRawContent)with no schema validation onpackageskeysimportPackage()— acceptstargetDirand passes it directly tocafsStore.importPackage(targetDir, ...)with zero containment checkEscalation to RCE (non-default config)
When
dangerouslyAllowAllBuilds: trueis configured (or the traversal package name is in the explicitallowBuildslist), the same traversed path is used in the rebuild phase atafter-install/src/index.ts:402,470. The attacker'spostinstallscript then executes with the victim's shell access. Under default config,allowBuildreturns false for unknown packages, limiting impact to arbitrary file write.Also Affected (PnP linker)
When
nodeLinker: pnpis configured,lockfileToPackageRegistry()atlockfile/to-pnp/src/index.ts:105-110uses the same unvalidateddp.parse().nameinpackageLocationconstruction, allowing the.pnp.cjsresolver map to point outside the virtual store. This is a lower-impact variant (PnP is not the default linker).Impact
An attacker who can commit a crafted
pnpm-lock.yamlto a repository (or supply one via a malicious package) can cause arbitrary file writes on the machine of any user who runspnpm install. Written content is the actual package files from a real npm package (attacker controls which package and which destination).Targets for arbitrary file write include:
.git/hooks/pre-commit— code execution on next git operation~/.local/bin/— binary hijackingReproduction
Craft a
pnpm-lock.yaml:Run
pnpm install— package content is written to/tmp/pwned/instead of the virtual store.Recommended Fix
Apply
safeJoinModulesDir(or equivalent validation) at:lockfileToDepGraph.ts:233—path.join(modules, pkgName)after-install/src/index.ts:402—path.join(pkgModulesDir(depPath), pkgInfo.name)lockfile/to-pnp/src/index.ts:105-110— PnPpackageLocationAlternatively, validate depPath keys during lockfile parsing to reject any that don't produce valid npm package names via
dp.parse().Relationship to GHSA-fr4h-3cph-29xv
GHSA-fr4h-3cph-29xv fixed the hoisted linker path (
lockfileToHoistedDepGraph.ts:222) by addingsafeJoinModulesDir. The same fix was NOT applied to the virtual store linker, which uses the identicaldp.parse().name → path.join()pattern atlockfileToDepGraph.ts:233.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: A tarball dependency's manifest
nameescapes node_modules → arbitrary file write/overwrite on installCVE-2026-82393 / GHSA-vq4v-j7r6-jq4m
More information
Details
Summary
When resolving a package, pnpm uses the resolved manifest
nameas a raw path segment for the isolated-linker import target. A tarball dependency whosepackage.jsonnameis a scoped path traversal (@x/../../…/<abs path>) is therefore extracted outsidenode_modules, to an attacker-chosen absolute path, and can overwrite existing files there. Attacker controls the destination, filenames, and contents → arbitrary file write → code execution (e.g.~/.zshrc,.git/hooks/pre-commit, another package's code). Occurs duringpnpm installeven with--ignore-scripts(no lifecycle scripts run), defeating that safety.Same class as the just-patched GHSA-hwx4 (transitive-dependency alias traversal) and GHSA-v23m (
stage downloadmanifest name/version traversal), in a sink their fixes did not cover: the isolated-linker import target keyed by the resolved name.Root cause
path.join(modules, <resolved name>)ininstalling/deps-resolver/src/resolvePeers.ts:706,installing/deps-resolver/src/index.ts:614, anddeps/graph-builder/src/lockfileToDepGraph.ts:233— without thesafeJoinModulesDirguard used on the symlink/hoisted/bin paths (installing/deps-restorer/src/lockfileToHoistedDepGraph.ts:222). The store location isnode_modules/.pnpm/<id>/node_modules/<name>, so a traversal<name>escapes.resolving/npm-resolver/src/pickPackage.ts:753) rejects only unscoped names containing/, so a scoped@x/../..passes.Steps to reproduce
Self-contained PoC (real
pnpm@11.9.0; loopback tarball server; escape target is a throwaway temp dir):Confirmed output (
repro/poc.mjs, exit 0):Remediation
Route the isolated-linker import-target joins (
resolvePeers.ts:706,deps-resolver/index.ts:614,lockfileToDepGraph.ts:233) throughsafeJoinModulesDir(as the hoisted linker already does), and/or enforcevalidate-npm-package-nameon the resolved manifest name (close the scoped-name gap atpickPackage.ts:753) so the import target rejects a traversal name and re-asserts containment before any write.Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
pnpm: Environment secrets exfiltrated via env-placeholder expansion in proxy settings read from an untrusted pnpm-workspace.yaml
CVE-2026-101043 / GHSA-vx52-2968-3vc6
More information
Details
Summary
pnpm expands
${VAR}environment placeholders in thehttpProxy/httpsProxy/noProxysettings read from a project'spnpm-workspace.yaml. Because a project manifest is repository-controlled, a malicious repository that a victim merely clones and runspnpm installin can route all install traffic through an attacker proxy whose hostname or userinfo embeds — and thereby exfiltrates — an environment secret such asNPM_TOKENorGITHUB_TOKEN.This bypasses a trust boundary pnpm deliberately enforces: env-placeholder expansion of request-destination settings is already suppressed for
registry,pnprServer,registriesandnamedRegistrieswhen they come from an untrusted project manifest, and the sibling.npmrcreader already classifies the proxy keys as request destinations. The manifest-side guard set simply omitted them.Impact
An attacker who controls only the contents of a repository's
pnpm-workspace.yaml— a public repo, a fork, or a supply-chain pull request — can read many values out of the victim's process environment and have them delivered to an attacker-controlled host. No pre-existing access to the victim's store, global config, lockfile,node_modules, or environment is required. The secret is exfiltrated during config loading, before any lifecycle script runs.This turns "I can author a project manifest" into "I read the victim's environment secrets."
Affected versions
Introduced in pnpm 10.7.0, which added environment-variable expansion in setting names and values.
>= 11.0.0, < 11.11.0>= 10.7.0, < 10.34.5The Rust port (
pacquet) and the registry server (pnpr) are not affected.Patches
The fix adds
httpProxy,httpsProxy,noProxy,proxyandnoproxyto the request-destination key set in@pnpm/config.reader(src/getOptionsFromRootManifest.ts), so env placeholders in proxy settings from an untrusted manifest are dropped rather than expanded — matching the existingregistry/pnprServerhandling and the.npmrcreader'sisRequestDestinationValueKey. Regression tests cover the proxy keys.Workarounds
Upgrade to a patched version. Until then, do not run pnpm commands in an untrusted repository in an environment that holds secrets, or inspect the repository's
pnpm-workspace.yamlfor proxy settings before installing.Proof of concept
With
NPM_TOKENset in the victim's environment,pnpm installexpands the placeholder and routes install traffic through the attacker's host, whose hostname (and DNS query) carries the token.Unit level:
Using
registryorpnprServerin place ofhttpsProxydoes not leak on either version — those keys were already guarded, which is what made the proxy keys a hole in an existing boundary rather than an unguarded surface.Credit
Reported privately. A second finding in the original report — the
Authorizationheader being retained across a same-hosthttps->httpredirect — was assessed and is not treated as a pnpm vulnerability: npm (make-fetch-happen,minipass-fetch), Yarn (got) and reqwest all compare host rather than origin, and a registry that redirects from HTTPS to plaintext HTTP is itself the broken component. That behavior is being discussed publicly at https://github.com/orgs/pnpm/discussions/13598.Severity
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
qs : qs.stringify with arrayFormat 'comma' throws an uncaught TypeError on array elements carrying a non-callable own toString (DoS)
CVE-2026-110816 / GHSA-6pq8-jhm2-2vcw
More information
Details
Summary
qs.stringify(obj, { arrayFormat: 'comma' })throws an uncaughtTypeError: Cannot convert object to primitive valuewhen an array inobjcontains an element that carries an own, non-callabletoStringproperty (for example{ a: [ { toString: 'x' } ] }). A remote, unauthenticated attacker who can influence an object that the application later re-serializes with the comma array format triggers the throw. In a service that lacks a synchronous error boundary around the serialization call, the uncaught exception crashes the worker process, producing a denial of service.Root Cause
When
arrayFormat: 'comma'is selected, the comma branch collapses the whole array into a single value viaArray.prototype.join, or, underencodeValuesOnly: true, maps each element through the value encoder before joining. Both paths perform an implicit string coercion (ToPrimitivewith hint"string") on each element. For an object whose owntoStringis a non-callable value, coercion skips the shadowed non-callabletoString, falls through toObject.prototype.valueOf(which returns the object rather than a primitive), and throws. The comma branch runs this coercion before the per-element null and skip handling in the main loop, so nothing downstream can neutralize the hostile element, and the throw is never caught inside the library.Impact
Prerequisites: the application calls
qs.stringifywitharrayFormat: 'comma'(a documented, supported option) on an object whose shape can be influenced by attacker input. Attacker-controllable sources includeqs.parse(input, { allowPrototypes: true }), which retains atoStringsegment (Express 4's default query parser, Express 5'sextendedquery parser, and body-parser'sextendedurlencoded parser all set it),qs.parse(input, { plainObjects: true }), whose null-prototype elements throw even without atoStringkey (eg,a[][b]=c), andJSON.parseof a request body, which needs no qs parse option at all. No authentication or user interaction is required. Demonstrated capability: a single request forces an uncaught synchronousTypeErrorin the serialization call. In a naive single-process Node service with no synchronous error boundary, this crashes the process and takes the service offline (availability impact). Under a framework such as Express that traps synchronous handler throws, the plausible outcome is a per-request 500 rather than a full outage; that lower-impact case is noted but not the worst credible supported deployment. There is no confidentiality or integrity impact, no memory unsafety, and no prototype pollution.Proof of Concept
Install qs at the affected version (for example 6.16.0) and run the following script with Node.js. No network setup is required; it demonstrates the throw in isolation.Attack Chain
qs.stringify(obj, { arrayFormat: 'comma' }).{ a: [ { toString: <non-callable value> } ] }, either viaa[0][toString]=xparsed withallowPrototypes, or via a JSON body{"a":[{"toString":"x"}]}.stringify, the primitive/buffer guard atlib/stringify.js:134is false for an array, so control reaches the comma branch atlib/stringify.js:149.encodeValuesOnlypath, the closurev == null ? v : encoder(v)atlib/stringify.js:152-154passes onlynull/undefinedthrough; a non-null object still reaches the encoder. The default path skips that closure entirely and reachesobj.join(',')atlib/stringify.js:156with no guard at all.Array.prototype.join, orString(str)atlib/utils.js:292).ToPrimitiveskips the non-callable owntoString,valueOfreturns the object, and aTypeErroris thrown.Bypass Evidence
This is a different element type on the code path CVE-2026-8723 (GHSA-q8mj-m7cp-5q26) patched, plus the default comma path, which that fix never covered. CVE-2026-8723 addressed a
TypeErrorcrash onnull/undefinedentries in the comma plusencodeValuesOnlypath. Its fix (commit 21f80b3) wraps the encoder infunction (v) { return v == null ? v : encoder(v); }, so it only passesnullandundefinedthrough. A non-null element with a hostile owntoStringfails thev == nulltest, so the encoder still runs and still throws; the encodeValuesOnly case (case C above) throws on the exact fixed code. The default comma path (encodeValuesOnlyunset, the library default) never entered that fix at all:git log -S "obj.join(','" -- lib/stringify.jsshows no security fix has ever touched theobj.join(',')line, and cases A and B throw withencodeValuesOnlyunset. The related isBuffer hardening for CVE-2026-82417 (commit e83d321) is a different sink at thelib/stringify.js:134guard, which is never entered for the array element; the negative control{a:{constructor:{isBuffer:'x'}}}serializes cleanly. Disproof attempts that failed: avalueOf-only object does not throw (it serializes to[object Object]), confirming the trigger is an element with no callabletoString(an own non-callabletoString, or a null-prototype object with notoStringat all, asqs.parseproduces underplainObjects); nested arrays ({"a":[[{"toString":"x"}]]}) throw the same way; a callabletoStringserializes cleanly; and the default (non-comma) array format serializes cleanly, confirming the comma format is required. All positive and negative cases were reproduced on the exact published 6.16.0 artifact.Affected Versions
Ecosystem: npmPackage: qsConfirmed affected range: >= 6.7.0, <= 6.16.0Latest release checked: 6.16.0 (npm latest; git tag v6.16.0)Fix status: fixed in 6.16.1 (a952e75)The
arrayFormat: 'comma'join sink was introduced in qs 6.7.0, so 6.7.0 is the lower boundary; releases before 6.7.0 lack the sink. No released version fixes this vector, and the latest published release (6.16.0) reproduces the throw, so the closed upper bound<= 6.16.0covers every affected release. TheencodeValuesOnlysub-path was added later, but the default comma-join vector already affects 6.7.0, so 6.7.0 governs the lower boundary.Suggested Fix
The comma array serialization must not allow an implicit string coercion of an array element to throw an uncaught exception. Coercion of each comma element should be performed defensively (for example by wrapping the join and per-element encode so that a coercion failure is caught and handled), and this protection must cover both the default
obj.join(',')path and theencodeValuesOnlyencoder path, not only the previously patchednull/undefinedcase. As an interim mitigation without a code change, applications can avoidarrayFormat: 'comma'when serializing data derived from untrusted input, sanitize parsed objects (avoidallowPrototypes/plainObjectson untrusted input and strip unexpected own properties), or wrapqs.stringifycalls in a synchronous error boundary so a coercion failure cannot crash the process.Reported by zx (GitHub: @manus-pi).
Independent report by @Lucidsec
The following was reported separately, after this advisory, and is merged here as a duplicate.
Summary
In project qs, in versions
>= 6.7.0, <= 6.16.0(every version that supportsarrayFormat: 'comma'),qs.stringifywitharrayFormat: 'comma'can throw an uncaughtTypeError: Cannot convert object to primitive valuewhen an array element has a non-callable owntoString. It is fixed in 6.16.1. Related: CWE-248 and CWE-704.Technical Details
qs.stringifywitharrayFormat: 'comma'joins an array atlib/stringify.js:156viaobj.join(','). An element whose owntoStringis not callable makesArray.prototype.jointhrow. There is no type check and no catch on that path.A second path exists with
encodeValuesOnly: atlib/stringify.js:151-154,maybeMaponly skipsv == null, thenlib/utils.js:292doesString(str)and throws the sameTypeError(this second site exists since v6.11.1; before that, the same input throws atjoin).Who can trigger: someone who can shape an array element passed to
stringify. The host must use non-defaultarrayFormat: 'comma'. DefaultarrayFormatindicesdoes not fold arrays and is not affected. A parse-then-stringify path also needsplainObjects: true(null-prototype elements fromlib/parse.js:200) orallowPrototypes: true(which Express 4's default query parser, Express 5'sextendedquery parser, and body-parser'sextendedurlencoded parser all set). Defaultqs.parse('a[0][toString]=x')returns{}because the prototype guard dropstoString. Direct JSON handed tostringifycan supply{"a":[{"toString":"x"}]}.Cause: the comma fold at
lib/stringify.js:149-156collapses the array into a single value before the main loop atlib/stringify.js:172-212, soskipNullsandstrictNullHandlingonly ever see the joined value, never individual elements.isNonNullishPrimitive/isBufferatlib/stringify.js:134inspect the whole value, not each element. A normal object usesObject.prototype.toStringand becomes[object Object]. An own non-callabletoStringis skipped,valueOfreturns the object, andjointhrows. (The skipped TODO test attest/stringify.js:684-689uses a plain{ b: 1 }element, which does not throw; it covers the separate question of what comma format should output for object elements, not this crash.) Earlier comma crash fixes did not cover object elements.Impact
One small input makes that serialization path throw a synchronous, catchable
TypeError. Behind a framework error boundary (eg, a synchronous Express handler), the request fails with a 500 and the process survives. In a host with no error boundary (eg, a barehttp.createServerhandler), the uncaught exception exits the process (the client seesECONNRESET). Availability only.Proof of Concept
Show the proof of concept
Checked on the latest commit that was checked:
07b1d4d82c8f9301c105ea4b94fa2302cfd6e8b4. Still present. Demonstrated.{"a":[{"toString":"x"}]}throws atlib/stringify.js:156.{"a":["x","y"]}returns 200 with bodya=x%2Cy.a=%5Bobject%20Object%5Dand does not throw.encodeValuesOnly: truethrows viautils.js:292.plainObjectsparse plus comma throws.Suggested Fix
Before
join, map elements with the existing primitive check; skip or replace non-primitives sojoincannot throw. On theencodeValuesOnlypath, do not send non-primitives intoString(). Optionally guardlib/utils.js:292. Add new regression tests.Severity
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Rustls: Session Debug formatting exposes TLS master secrets and resumption PSKs
GHSA-c8m5-c952-6cm4 / RUSTSEC-2026-0346
More information
Details
Summary
Formatting a client session value with Debug exposes its TLS 1.2 master secret or TLS 1.3 resumption PSK. A custom session store that logs these values can disclose keys to readers of the logs. Ordinary connection and built-in cache diagnostics remain redacted, and the session-store interface already warns that its values are sensitive.
Details
Derived formatters traverse the session's secret-bearing fields. This conflicts with the project's explicit key-material redaction policy.
Proof of concept
Custom
ClientSessionStoreimplementations could leak secret data by using the providedTls12ClientSessionValueorTls13ClientSessionValuein a way that triggers its implementation of theDebugtrait.Impact
Such store implementations could leak the master secret for the TLS connection.
Acknowledgement
Thanks to OpenAI OutboundDisclosures@openai.com for the disclosure.
Severity
CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:H/I:N/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Rustls: Brotli certificate decompression can allocate an attacker-controlled amount of memory up to 1 GiB before authentication.
GHSA-h2v8-394m-p7vv / RUSTSEC-2026-0348
More information
Details
Summary
The built-in (but disabled by default) Brotli certificate decoder bounds the output buffer but accepts an input-selected history window. A header of five bytes can specify a 1 GiB window allocation. A peer can abort a process whose allocator cannot satisfy that request. A client must enable Brotli and connect to the peer; a server must enable Brotli and request a client certificate. Neither direction requires a trusted attacker certificate.
Details
rustls calls
BrotliDecompresswhich enables Brotli's large-window extension. rustls applies a limit to certificates, but this applies to the decompressed result, and not to any internal allocations the process requires.Impact
Client applications that enable the
brotlifeature will be vulnerable to malicious servers. Server applications that enable thebrotlifeature and enable client authentication will be vulnerable to malicious clients.Potential for denial of service due to memory exhaustion from peers sending malicious Brotli-encoded certificates, for applications that enable Brotli decompression.
Acknowledgement
Thanks to OpenAI OutboundDisclosures@openai.com for the disclosure.
Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:LReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
rustls: ECH rejection can expose buffered application data to authenticated outer server before
ech_requiredalertGHSA-vp88-798v-h67f / RUSTSEC-2026-0347
More information
Details
In rustls 0.23.44 and 0.23.45, application data that a client buffers before the handshake completes is sent to the server even when the server rejects Encrypted Client Hello (ECH).
When the server rejects ECH, the client completes the handshake with the outer ClientHello and authenticates the server against the ECH config's
public_name. At the end of the handshake,start_traffic()encrypts and queues any plaintext buffered byClientConnection::writer(). It does this before checking forEchStatus::Rejectedand queueing theech_requiredalert.The application then receives
Error::PeerIncompatible(ServerRejectedEncryptedClientHello), as expected. However, the client is still expected to callwrite_tls()to send the fatal alert, and that call also sends the encrypted application data. The server decrypts the data and only then receives the alert.The ordering bug is older than 0.23.44. Before 0.23.44, the client checked the server certificate against the inner server name rather than
public_name, so a correctly configured server failed certificate verification first and no data was sent. 0.23.44 fixed that check, which makes the ordinary rejection path reachable.Impact
Plaintext meant for the inner server, the one named in the encrypted ClientHello, goes to the client-facing server identified by
public_name. That server is properly authenticated, so this is not an attack by an unauthenticated party. However this violates RFC 9849, which says the following:public_name, and the connection "MUST NOT" be reported as successful to the application. rustls does report failure correctly. The problem is only that data is sent before the alert.This affects only clients that meet all of these conditions:
EchMode::Enable. GREASE ECH is not affected.The usual client response to rejection is to retry with the server's retry configs. In that case, the data has already gone to the outer server.
Severity
CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
rustls: An unauthenticated ServerHello poisons the per-name key-exchange record, so later ClientHellos carry only a classical X25519 share
GHSA-w44r-93j7-qv28 / RUSTSEC-2026-0349
More information
Details
Summary
The client records the server-selected key-exchange group per server name when it processes the ServerHello, before the server is authenticated. A network attacker can answer one connection attempt for a target name with a ServerHello that selects X25519 and close the connection. Later ClientHellos for that name carry one X25519 share while supported_groups still lists X25519MLKEM768 first. If the server accepts that share instead of sending a HelloRetryRequest, the handshake completes with classical key exchange only and no error.
An attacker that answers one connection attempt for the target name with a ServerHello selecting X25519 changes every later ClientHello of that client for that name, for as long as the entry stays in the store. No certificate or completed handshake is needed: it parses the ClientHello, sends the ServerHello, and closes.
TLS 1.3 transcript binding stops an on-path attacker from editing the ClientHello of one connection. It does not stop the attacker from steering the client's prediction state beforehand, because that state is written from an unauthenticated message.
What does not trigger it
Impact
If the genuine server picks the group for which a share is present, instead of sending a HelloRetryRequest for its preferred hybrid group, the handshake completes with classical key exchange only, between two peers that both support the hybrid group. No error is raised. The confidentiality of recorded sessions against a future quantum adversary is lost for those connections (harvest now, decrypt later). There is no key compromise and no authentication bypass.
Note that this behaviour is discussed at length in draft-ietf-tls-key-share-prediction so the actual security impact is largely down to a server policy decision.
Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:NReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Debugimpls for client session values expose TLS secretsGHSA-c8m5-c952-6cm4 / RUSTSEC-2026-0346
More information
Details
The
Debugimplementations ofTls12ClientSessionValueandTls13ClientSessionValuewere derived, and so included the TLS 1.2 mastersecret or TLS 1.3 resumption PSK in their output.
Custom
ClientSessionStoreimplementations that format these values withDebug(for example, when logging) could disclose these secrets to readers ofthe logs. This conflicts with rustls's
key-material redaction policy.
Ordinary connection and built-in session cache
Debugoutput is redacted andis not affected.
Severity
CVSS:3.1/AV:L/AC:H/PR:H/UI:R/S:U/C:H/I:N/A:NReferences
This data is provided by OSV and the Rust Advisory Database (CC0 1.0).
Buffered application data sent to outer server after ECH rejection
GHSA-vp88-798v-h67f / RUSTSEC-2026-0347
More information
Details
When a server rejects Encrypted Client Hello (ECH), the client completes the
handshake using the outer ClientHello and authenticates the server against the
ECH config's
public_name. At the end of that handshake, rustls encrypted andqueued any application data buffered via
ClientConnection::writer()beforequeueing the
ech_requiredalert.The application correctly receives
Error::PeerIncompatible(ServerRejectedEncryptedClientHello), but the nextwrite_tls()call (needed to send the alert) also sends the bufferedapplication data. Plaintext meant for the inner server therefore goes to the
client-facing server named by
public_name.That server is authenticated, so this is not exploitable by an unauthenticated
party, but it violates RFC 9849 section 6.1.6, which requires the client to
abort with
ech_required"before sending any application data to the server".This affects only clients that:
EchMode::Enable(GREASE ECH is not affected),The underlying ordering bug is older, but before 0.23.44 the client checked
the server certificate against the inner server name rather than
public_name,so certificate verification failed first and no data was sent.
Severity